
require 'digest/sha1'

# require_gem 'acts_as_taggable' - no user self tagging for now xxx?

class User < ActiveRecord::Base

    #acts_as_taggable

    # basic  validation
    validates_length_of :login, :within => 3..20
    validates_length_of :password, :within => 5..20
    validates_presence_of :login, :email, :password, :password_confirmation, :salt
    validates_uniqueness_of :login #, :email
    validates_confirmation_of :password

    validates_format_of :login,
                        :with => /^([a-z0-9_]+){0,2}[a-z0-9_]+$/i,
                        :on => :create,
                        :message => "can only contain letters and digits"

    validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, 
              :message => "Invalid email"  
  
    attr_protected :id, :salt
    attr_accessor :password, :password_confirmation
    #attr_accessor :referer
    attr_accessor :family_name # hack remove xxx

    # groups that this observer is a member of above and beyond those they created
    has_many :groups_users, :dependent => :nullify
    has_many :groups, :through => :groups_users

    # notes that this observer is observing above and beyond notes they created
    has_many :notes_users, :dependent => :nullify
    has_many :notes, :through => :notes_users

    def self.find_named(named)
      return nil if !named || !named.length
      return User.find(:first,:conditions => ["login ilike ?",named])
    end

    def self.authenticate(login, pass)
      #u=find(:first, :conditions=>["login = ?", login])
      u= self.find_named(login)
      return nil if u.nil?
      return nil if u.openid_url && u.openid_url.length > 1 # openid cannot log in this way
      return u if User.encrypt(pass, u.salt)==u.salted_password
      nil
    end
  
    def relative_url
      # xxx unused:
      # can't really use this since these services can be mounted anywhere
      # also this is more of a controller role - could put in a helper
      "/#{self.login}"
    end
  
    def password=(pass)
      @password=pass
      self.salt = User.random_string(10) if !self.salt?
      self.salted_password = User.encrypt(@password, self.salt)
    end
  
    def send_new_password
      pass = User.random_string(10)
      self.password = self.password_confirmation = pass
      self.save
    end

    def set(params)
      params[:uid] = params[:login]
      params.each { |x,y|
        self.send(x.to_s+'=',y.to_s) if x && y && y.to_s.length > 0 && self.has_attribute?(x)
      }
      if params[:password_confirmation]
        self.password_confirmation = params[:password_confirmation]
      end
      if params[:password]
        self.password = params[:password]
      end
    end

=begin

    RAND_CHARS = [*'A'..'Z'] + [*'0'..'9'] + [*'a'..'z']

    #
    # make provisional accounts
    #
    def User.create_provisional_user(params)

      # get name and email
      name = params[:name]
      email = params[:email]
      role = params[:role]

      # quick error check for convenience
      if !name || !email
        return nil,"Name or email is missing"
      end

      # turn the english name into a login - finding a legal name
      login_ideal = login = name.gsub('/([^a-zA-Z0-9])/','_')
      counter = nil
      login = nil
      begin
        login = "#{login_ideal}#{counter}"
        counter = 0 if !counter
        counter = counter + 1
      end until !User.find_by_uid(login)

      # generate a key
      rand_max = RAND_CHARS.size
      password = (0...16).inject("") { |ret,_| ret << RAND_CHARS[rand(rand_max)] }

      # make the provisional user
      @nuser = nil
      params[:uid] = login
      params[:login] = login
      params[:email] = email
      params[:password] = password
      params[:password_confirmation] = password
      begin
        @nuser = User.new
        @nuser = User.set(params)
      rescue
        return nil,"user creation seemed to have bad parameters?"
      end
      if !@nuser
        return nil,"Signup unsuccessful due to bad parameters"
      end
      @nuser.password = @nuser.password_confirmation = params[:password]
      if !@nuser.save
        errors = ''
        @nuser.errors.each do |alpha,beta|
          errors = "#{errors}<br/>#{alpha}, #{beta}"  
        end
        return nil,errors
      end

      # send a message asking user to change their password
      # xxx move up
      begin
        mess = "From: noreply@youthgive.org\n" +
               "To: #{email}\n" +
               "\n" +
               "You have been given an account on youthgive\n" +
               "Please visit this URL to finish setting up your account: " +
               "http://youthgive.org/#{login}/confirm?key=#{password}"
        Net::SMTP.start('localhost',25) do |smtp|
          smtp.send_message mess,"noreply@youthgive.org",email
        end
      rescue
        @nuser.destroy()
        return nil,"Internal error sending email to this email address"
      end

      return @nuser,nil
    end
=end

  protected
  
    def self.encrypt(pass, salt)
      Digest::SHA1.hexdigest(pass+salt)
    end
  
    def self.random_string(len)
      #generate a random password consisting of strings and digits
      chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
      newpass = ""
      1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
      return newpass
    end

    def self.schema
      self.schema_001
    end

    def self.schema_drop
      return if !table_exists?
      ActiveRecord::Schema.define do
        drop_table :users
      end
    end

    def self.schema_001
      return if table_exists?
      ActiveRecord::Schema.define do
        begin
          create_table :tags do |t|
            # t.column :id,              :serial, :null => false
            t.column :name,            :text
          end
        rescue
        end
        begin
          create_table :users_tags do |t|
            t.column :tag_id,          :integer, :null => false
            t.column :user_id,         :integer, :null => false
          end
          remove_column :users_tags, :id
        rescue
        end
        create_table :users do |t|
          #t.column :id,              :serial, :null => false

          # properties from 'notes' object which we create a facade object for now
          # it would be safer to inherit notes somehow but i don't see how to do this without mess
          t.column :uid,             :text
          t.column :parent,          :integer
          t.column :handler,         :text
          #t.column :type,            :text

          t.column :title,           :text
          t.column :link,            :text
          t.column :description,     :text
          t.column :tagstring,       :text
          t.column :depiction,       :text
          t.column :extended,        :text

          t.column :sponsor,         :integer
          t.column :perms,           :integer
          t.column :nchildren,       :integer
          t.column :nobservers,      :integer
          t.column :location,        :text
          t.column :lat,             :float
          t.column :lon,             :float
          t.column :begins,          :datetime
          t.column :ends,            :datetime
          t.column :created_at,      :datetime
          t.column :modified_at,     :datetime

          # user properties
          t.column :login,           :string , :limit => 32, :null => false
          t.column :email,           :string , :limit => 90, :null => false, :default => ''
          t.column :salted_password, :string , :limit => 40, :null => false, :default => ''
          t.column :salt,            :string , :limit => 40, :null => false, :default => ''
          t.column :firstname,       :text
          t.column :lastname,        :text
          t.column :age,             :integer, :default => 0
          t.column :sex,             :string, :limit => 80, :default => ''
          t.column :status,          :string, :limit => 80, :default => ''
          t.column :address,         :string, :limit => 255, :default => ''
          t.column :homepage,        :string, :limit => 255, :default => ''
          t.column :websites,        :text
          t.column :openid_url,      :text
          t.column :verified,        :integer, :default => 0
          t.column :role,            :string , :limit => 40, :default => ''
          t.column :security_token,  :string , :limit => 40
          t.column :token_expiry,    :datetime
          t.column :logged_in_at,    :datetime
          t.column :deleted,         :integer, :default => 0
          t.column :delete_after,    :datetime
          t.column :family,          :integer
          t.column :referer,         :integer
          t.column :referee,         :integer
          t.column :parent_email,    :integer
        end
      end
    end
end

#
# Make user schema right now! if needed
#
User.schema
